package org.kleathome.dcpu16;

import java.util.Formatter;
import java.util.Locale;
import java.util.Vector;
import org.kleathome.dcpu16.asm.ASMAddress;
import org.kleathome.dcpu16.asm.ASMLabel;
import org.kleathome.dcpu16.asm.ASMLiteral;
import org.kleathome.dcpu16.asm.ASMRegister;
import org.kleathome.dcpu16.emulator.Emulator;

public abstract class DCPU16Instruction
{
	protected DCPU16Value valueA;
	protected DCPU16Value valueB;
	private boolean useValueA = true;
	static private Vector<DCPU16Instruction> _instructions = null;
	
	public abstract int getOpCode();
	public abstract String getOpCodeLiteral();
	public abstract void execute();	// TODO something with cycles
	public abstract DCPU16Instruction getNewObject();
	public void setA(char value)
	{
		valueA = DCPU16Value.getValue(value);
	}
	public void setB(char value)
	{
		valueB = DCPU16Value.getValue(value);
	}
	public String toString()
	{
		return getOpCodeLiteral() + " " + valueA + ", " + valueB;
	}
	public boolean wants(String in)
	{
		if (in.trim().toLowerCase().startsWith(getOpCodeLiteral().toLowerCase()))
			return true;
		return false;
	}
	
	private void setValue(DCPU16Value value)
	{
		if (useValueA)
			valueA = value;
		else
			valueB = value;
	}
	private String getNextKeyword(String in)
	{
		String keyword = "";
		for (int i = 0; i < in.length(); ++i)
		{
			char k = in.toLowerCase().charAt(i);
			if (Character.isWhitespace(k) || ',' == k || '[' == k || ';' == k)
			{
				keyword = in.substring(0, (i==0?1:i));
				break;
			}
		}
		return keyword;
	}
	private String parseAddress(String in)
	{
		// parse address
		String what = in;
		for (int i = 0; i < what.length(); ++i)
		{
			char c = what.charAt(i);
			if (Character.isWhitespace(c) || c == ']')
			{
				String address = what.substring(0, i);
				setValue(new ASMAddress(address));
				in = in.substring(1 + address.length());
				break;
			}
		}
		return in;
	}
	private void switchValue(String in)
	{
		// switch from a to b, else error
		if (useValueA)
			useValueA = false;
		else
		{
			System.err.println("too many colons near " + in);
			System.exit(1);
		}
	}
	public String parseInstruction(String in)
	{
		// can either be Register, Address, RegisterAddress, Label or Literal
		// Address always starts with [
		// Register is keyword A B C X Y Z I J PC SP O
		// literal is anything starting with a digit 
		// label is anything that is not literal
		
		useValueA = true;
		
		while (in.length() > 0)
		{
			in = in.trim();
			String keyword = getNextKeyword(in);
			in = in.substring(keyword.length());
			
			if (keyword.equals(";"))
			{
				return "";
			}
			else if (keyword.equals("["))
			{
				in = parseAddress(in);
			}
			else if (keyword.equals(","))
			{
				switchValue(in);
			}
			else if (Emulator.Registers.isRegister(keyword))
			{
				// parse register
				setValue(new ASMRegister(keyword));
			}
			else if (Character.isLetter(keyword.charAt(0)))
			{
				// parse label
				if (keyword.equalsIgnoreCase("pop") || keyword.equalsIgnoreCase("push") || keyword.equalsIgnoreCase("peek"))
				{
					setValue(new ASMAddress(keyword));
				}
				else
					setValue(new ASMLabel(keyword));
			}
			else if (Character.isDigit(keyword.charAt(0)))
			{
				// parse number
				setValue(new ASMLiteral(keyword));
			}
		}
		return in;
	}
	public int getCycleCost()
	{
		return (valueA==null?0:valueA.getCycleCost()) + (valueB==null?0:valueB.getCycleCost());
	}
	public static Vector<DCPU16Instruction> getInstructions()
	{
		if (_instructions == null)
		{
			_instructions = new Vector<DCPU16Instruction>();
			_instructions.add(new DCPU16Extended());
			_instructions.add(new DCPU16Set());
			_instructions.add(new DCPU16Add());
			_instructions.add(new DCPU16Sub());
			_instructions.add(new DCPU16Mul());
			_instructions.add(new DCPU16Div());
			_instructions.add(new DCPU16Mod());
			_instructions.add(new DCPU16Shl());
			_instructions.add(new DCPU16Shr());
			_instructions.add(new DCPU16And());
			_instructions.add(new DCPU16Bor());
			_instructions.add(new DCPU16Xor());
			_instructions.add(new DCPU16Ife());
			_instructions.add(new DCPU16Ifn());
			_instructions.add(new DCPU16Ifg());
			_instructions.add(new DCPU16Ifb());
		}
		return _instructions;
	}
	public static DCPU16Instruction getInstruction(char opcode)
	{
		for (DCPU16Instruction ins: getInstructions())
		{
			if (ins.getOpCode() == opcode)
				return ins;
		}
		return null;
	}
	public char getInstructionWords()
	{
		return (char)(1 + (valueA==null?0:valueA.getInstructionWords()) + (valueB==null?0:valueB.getInstructionWords()));
	}
	public void updateJumpAddresses()
	{
		if (valueA != null)
			valueA.updateJumpAddresses();
		if (valueB != null)
			valueB.updateJumpAddresses();
	}
	public String getASCIICode()
	{
		StringBuilder sb = new StringBuilder();
		Formatter formatter = new Formatter(sb, Locale.ENGLISH);
		
		char[] instruction = new char[getInstructionWords()];
		int index = 0;
		instruction[index] = (char) getOpCode();
		instruction[index] += (char)(valueA.getOpCode() << 4);
		if (valueB != null)
			instruction[index] += (char)(valueB.getOpCode() << 10);
		formatter.format("%04x", (int)instruction[index++]);
		if (valueA.getInstructionWords() > 0)
		{
			instruction[index] = valueA.getDataCode();
			formatter.format("%04x", (int)instruction[index++]);
		}
		if (valueB != null && valueB.getInstructionWords() > 0)
		{
			instruction[index] = valueB.getDataCode();
			formatter.format("%04x", (int)instruction[index++]);
		}
		return sb.toString();
	}
	public boolean hasValue(DCPU16Value in)
	{
		if (valueA == in || valueB == in)
			return true;
		return false;
	}
}
